10 您所在的位置:网站首页 vue admin element store 文件 10

10

2023-07-24 22:24| 来源: 网络整理| 查看: 265

项目配置 vue.config.js

在根目录下创建vue.config.js,做一些相应的配置

//为什么是module.exports,因为此文件的执行环境是node环境 module.exports={ publicPath:'/best-practice',//部署应用包时的基本URL devServer:{ port:port }, configureWebpack:{ // 想index.html注入标题 name:'vue最佳实践' } } // index.html——使用webpack中配置的name 复制代码优雅使用icon——svg icon发展史 最初的时候,大部分图标都是用img来实现的,如果一个项目中涉及到很多图标的时候,那一个页面中的请求资源中img占了大部分; 后面慢慢出现了雪碧图,将很多图标整合到一个图片上,css利用background-position 定位显示不同的 icon 图标,比起最开始img的方式,雪碧图已经有了很大的优化,但是依然有一个非常大的痛点,那就是每新增一个图标,就得去更新原先的雪碧图,这样做风险是很大的,不小心影响了原先图标的位置时,那页面中某些图标就加载不出来了,这种方式是极难维护的。 Font Awesome 出现,Font Awesome官网的体验不是太好,图标很小,找起来相当费力。 iconfont出现,阿里的开源库,找起来相当轻松,而且图标数量相当可人。 iconfont使用方式

先总体讲一下,在iconfont官网上将需要的图标打包下载下来后,如下:

将上面的这些包放到项目中,如果仅仅用unicode和font-class的方式来展示图标的话,那么仅仅需要引入iconfont.css即可,如果需要增加图标,新下载了图标包下来后,只需要将iconfont.css文件更新一下即可。

如果使用svg的方式展示图标的话,这时候起作用的是iconfont.js文件,如果图标有更新的话,将iconfont.js文件替换掉就行。

unicode

Unicode 是字体在网页端最原始的应用方式,特点是:

兼容性最好,支持 IE6+,及所有现代浏览器。 支持按字体的方式去动态调整图标大小,颜色等等。 但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。

注意:新版 iconfont 支持多色图标,这些多色图标在 Unicode 模式下将不能使用,如果有需求建议使用symbol 的引用方式

Unicode 使用步骤如下:

第一步:引入iconfont.css文件

// iconfont.css @font-face {font-family: "iconfont"; src: url('iconfont.eot?t=1593657248234'); /* IE9 */ src: url('iconfont.eot?t=1593657248234#iefix') format('embedded-opentype'), /* IE6-IE8 */ //更新的时候最重要的是更新这一部分 url('data:application/x-font-woff2;base64,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') format('woff2'), url('iconfont.woff?t=1593657248234') format('woff'), url('iconfont.ttf?t=1593657248234') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url('iconfont.svg?t=1593657248234#iconfont') format('svg'); /* iOS 4.1- */ } .iconfont { font-family: "iconfont" !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-yundong-:before { content: "\e611"; } .icon-yundong:before { content: "\e6ea"; } .icon-zhoubao:before { content: "\e68c"; } // 新增的图标后面直接加入即可 .icon-meilishuo:before { content: "\ea15"; } .icon-meilishuo1:before { content: "\e78e"; } 复制代码

第二步:挑选相应图标并获取字体编码,应用于页面

3 复制代码

font-class

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

与 Unicode 使用方式相比,具有如下特点:

兼容性良好,支持 IE8+,及所有现代浏览器。 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。 不过因为本质上还是使用的字体,所以多色图标还是不支持的。

font-class 使用步骤如下:

第一步:引入iconfont.css文件

复制代码

注意:修改iconfont.css文件的方式与unicode的方式是一样的

第二步:挑选相应图标并获取类名,应用于页面:

复制代码

symbol

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

支持多色图标了,不再受单色限制。 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。 兼容性较差,支持 IE9+,及现代浏览器。 浏览器渲染 SVG 的性能一般,还不如 png。

使用步骤如下:

第一步:引入项目下面生成的 symbol 代码:

复制代码

第二步:加入通用 CSS 代码(引入一次就行):

.icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } 复制代码

第三步:挑选相应图标并获取类名,应用于页面:

复制代码 vue项目中使用svg 使用svg好处 支持多色图标了,不再受单色限制。 支持像字体那样通过font-size,color来调整样式。 支持 ie9+ 可利用CSS实现动画。 减少HTTP请求。 矢量,缩放不失真 可以很精细的控制SVG图标的每一部分

使用svg-icon的好处是我再也不用发送woff|eot|ttf| 这些很多个字体库请求了,所有的svg都可以内联在html内。 svg是真正的矢量图,放大缩小都不会失真。

安装loader mpm i svg-sprite-loader -D 复制代码

svg-sprite-loader 将加载的 svg 图片拼接成雪碧图,放到页面中,其它地方通过 复用。

本来只添加svg-sprite-loader就行了,但是svg也是图片的一种,所以file-loader也会对其进行处理,所以就会冲突,解决的办法就是,在项目中新建一个文件icons,使用file-loader编译svg的时候不编译icons里面的图标

添加配置——vue.config.js

vue inspect --rules:可查看webpack中有哪些相关的规则

[ 'vue', 'images', 'svg', 'media', 'fonts', 'pug', 'css', 'postcss', 'scss', 'sass', 'less', 'stylus', 'js', 'eslint', 'icons'] 复制代码

vue inspect --rule svg:单独查看某一项在webpack中的配置

// 对svg处理的默认配置:使用file-loader { test: /\.(svg)(\?.*)?$/, use: [ { loader: 'file-loader', options: { name: 'static/img/[name].[hash:8].[ext]' } } ] } 复制代码

添加链式操作

vue-cli默认情况下使用file-loader对svg进行处理,会将svg放到/img目录下,现在需要更改webpack配置,使用svg-sprite-loader处理svg`。

直接将,默认配置中的test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,中的svg删掉,不过这样处理是有风险的,因为项目中可能有的svg确实需要当图片资源使用,而且第三方库中也有可能使用到svg

在webpack中重新做配置,只处理需要处理的那一部分svg

module.exports={ publicPath:'/best-practice',//部署应用包时的基本URL devServer:{ port:port }, configureWebpack:{ // 想index.html注入标题 name:'vue最佳实践' }, chainWebpack(config){ // set svg-sprite-loader // 1.svg rule中要排除icons目录 config.module .rule('svg') .exclude.add(resolve('src/icons')) .end() // 2加一个规则icons config.module .rule('icons') .test(/\.svg$/) .include.add(resolve('src/icons')) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }) .end() } } 复制代码自动导入svg图标

// icons/index.js const req = require.context('./svg', false, /\.svg$/) req.keys().map(req); // main.js import './icons' 复制代码

使用:

复制代码

其实到这一步,项目中已经可以随意使用svg图标了,为了能统一处理svg图标的样式以及代码的可阅读性,咱们一起去创建一个svg的公用组件去~

创建svg组件 svg组件 // doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage import { isExternal } from '@/utils/validate' export default { name: 'SvgIcon', props: { iconClass: { type: String, required: true }, className: { type: String, default: '' } }, computed: { isExternal () { return isExternal(this.iconClass) }, iconName () { return `#icon-${this.iconClass}` }, svgClass () { if (this.className) { return 'svg-icon ' + this.className } else { return 'svg-icon' } }, styleExternalIcon () { return { mask: `url(${this.iconClass}) no-repeat 50% 50%`, '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%` } } } } .svg-icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } .svg-external-icon { background-color: currentColor; mask-size: cover!important; display: inline-block; } 复制代码 全局注册 import Vue from 'vue' import SvgIcon from '@/components/SvgIcon' // 全局注册svg组件 Vue.component('svg-icon', SvgIcon) 复制代码 使用 复制代码权限控制及动态路由 路由定义

路由分为两种:constantRoutes 和 asyncRoutes

constantRoutes是常规路由,不需要守卫,用户可直接访问

export const constantRoutes = [ { path: '/login', component: () => import('@/views/login/index'), hidden: true }, { path: '/404', component: () => import('@/views/error-page/404'), hidden: true }, { path: '/401', component: () => import('@/views/error-page/401'), hidden: true } ] 复制代码

asyncRoutes:权限页面,用户需要登录并且拥有访问的权限角色才能访问

export const asyncRoutes = [ { path: "/about", component: Layout, redirect: "/about/index", children: [ { path: "index", component: () => import(/* webpackChunkName: "home" */ "@/views/About.vue"), name: "about", meta: { title: "About", icon: "qq", roles: ['admin', 'editor'] }, } ] } ] export default new Router({ mode: "history", base: process.env.BASE_URL, routes: constRoutes }) 复制代码路由守卫 // 白名单 const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist if (whiteList.indexOf(to.path) !== -1) { // 白名单, go directly next() } else { api.shadow().then(async () => { const hasRoles = store.getters.roles && store.getters.roles.length > 0 if (hasRoles) { next() } else { try { // 获取权限 const roles = await store.dispatch('user/getInfo') resetRouter() // 挂载路由 const accessRoutes = await store.dispatch('permission/generateRoutes', roles) router.addRoutes(accessRoutes) next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record } catch (error) { next('login') } } }).catch(() => { next('login') }) } 复制代码添加动态路由

根据用户角色过滤出可访问的路由并动态添加到router。

创建permission模块,store/modules/permission.js

import { asyncRoutes, constantRoutes } from '@/router' /** * Use meta.role to determine if the current user has permission * @param roles * @param route */ function hasPermission(roles, route) { if (route.meta && route.meta.roles) { return roles.some(role => route.meta.roles.includes(role)) } else { return true } } /** * Filter asynchronous routing tables by recursion * @param routes asyncRoutes * @param roles */ export function filterAsyncRoutes(routes, roles) { const res = [] routes.forEach(route => { const tmp = { ...route } if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterAsyncRoutes(tmp.children, roles) } res.push(tmp) } }) return res } const state = { routes: [], addRoutes: [] } const mutations = { SET_ROUTES: (state, routes) => { state.addRoutes = routes state.routes = constantRoutes.concat(routes) } } const actions = { generateRoutes({ commit }, roles) { return new Promise(resolve => { let accessedRoutes if (roles.includes('admin')) { accessedRoutes = asyncRoutes || [] } else { accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) } commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) } } export default { namespaced: true, state, mutations, actions } 复制代码

注意:我在公司项目里面生成动态路由的时候就犯难了,思考了很多,把有权限的路由与菜单栏展示一起考虑了,用户既有权限访问这些有权限的路由,然而某些有权限的路由还不能在菜单栏上呈现出来,比如有一些二级的详情页,菜单栏上不需要呈现这一项菜单的,当时候思考了很多,思绪也比较混乱,这次重新整理这部分的时候,思路突然清晰了很多,动态路由就是将所有有权限的路由都挂载上,这里不要跟菜单栏混淆,菜单栏渲染的那里有自己的逻辑处理,这样就各自处理各自的逻辑就好了。

按钮权限 封装按钮权限指令

封装一个指令v-permission,从而实现按钮级别的权限控制,在src下创建directive目录,创建permission.js

// src/directive/permission.js import store from '@/store' export default { inserted(el, binding, vnode) { const { value } = binding const roles = store.getters && store.getters.roles if (value && value instanceof Array && value.length > 0) { const permissionRoles = value const hasPermission = roles.some(role => { return permissionRoles.includes(role) }) if (!hasPermission) { el.parentNode && el.parentNode.removeChild(el) } } else { throw new Error(`need roles! Like v-permission="['admin','editor']"`) } } } 复制代码 全局注册指令

在main.js中,将自定义的权限指令全局注册

import permission from "./directive/permission"; Vue.directive("permission", permission); 复制代码 v-permission指令的使用 admin button 复制代码 element中el-tabs使用指令无效的问题

上面封装的v-permission指令,只能删除挂载指令的元素,对于额外生成的和指令无关的元素是不生效的

用户管理 配置管理 角色管理 定时任务补偿 复制代码

在el-tab-pane标签上加上v-permission指令后,根据上图中生成的代码,可发现,也仅仅是将此tab下的内容设置了权限,而几个可切换的tab并没有受到权限的控制,你会发现页面展示的时候,3个tab都存在,也可正常切换,但是每个tab下的内容会根据权限来展示与否,虽然也算是曲线实现了权限控制这个功能,但是总是不太优雅,此时只能用v-if来实现这个功能了:

在utils文件下创建一个checkPermission公用的方法:

import store from '@/store' /** * @param {Array} value * @returns {Boolean} * @example see @/views/permission/directive.vue */ export default function checkPermission(value) { if (value && value instanceof Array && value.length > 0) { const roles = store.getters && store.getters.roles const permissionRoles = value const hasPermission = roles.some(role => { return permissionRoles.includes(role) }) if (!hasPermission) { return false } return true } else { console.error(`need roles! Like v-permission="['admin','editor']"`) return false } } 复制代码 v-if实现权限校验的使用 复制代码导航菜单生成

导航菜单是根据路由信息并结合权限判断而动态生成的。它需要支持路由的多级嵌套,所以这里要用到递归组件。

创建:layout/Sidebar/index.vue

import { mapGetters } from 'vuex' import Logo from './Logo' import SidebarItem from './SidebarItem' import variables from '@/styles/variables.scss' export default { components: { SidebarItem, Logo }, computed: { ...mapGetters([ 'permission_routes', 'sidebar' ]), activeMenu() { const route = this.$route const { meta, path } = route // if set path, the sidebar will highlight the path you set if (meta.activeMenu) { return meta.activeMenu } return path }, showLogo() { return this.$store.state.settings.sidebarLogo }, variables() { return variables }, isCollapse() { return !this.sidebar.opened } } } 复制代码

创建:layout/Sidebar/SidebarItem.vue

import path from 'path' import { isExternal } from '@/utils/validate' import Item from './Item' import AppLink from './Link' import FixiOSBug from './FixiOSBug' export default { name: 'SidebarItem', components: { Item, AppLink }, mixins: [FixiOSBug], props: { // route object item: { type: Object, required: true }, isNest: { type: Boolean, default: false }, basePath: { type: String, default: '' } }, data() { // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237 // TODO: refactor with render function this.onlyOneChild = null return {} }, methods: { hasOneShowingChild(children = [], parent) { const showingChildren = children.filter(item => { if (item.hidden) { return false } else { // Temp set(will be used if only has one showing child) this.onlyOneChild = item return true } }) // When there is only one child router, the child router is displayed by default if (showingChildren.length === 1) { return true } // Show parent if there are no child router to display if (showingChildren.length === 0) { this.onlyOneChild = { ... parent, path: '', noShowingChildren: true } return true } return false }, resolvePath(routePath) { if (isExternal(routePath)) { return routePath } if (isExternal(this.basePath)) { return this.basePath } return path.resolve(this.basePath, routePath) } } } 复制代码

注意:使用了递归组件,递归组件必须设置name

面包屑

面包屑导航是通过$route.matched数组动态生成的,面包屑是根据路由嵌套来生成的,所以设置路由的时候要根据业务去配置好。

{{ $t('sidebar.'+item.meta.title) }} {{ $t('sidebar.'+item.meta.title) }} import pathToRegexp from 'path-to-regexp' export default { data () { return { levelList: null } }, watch: { $route: { handler (route) { this.getBreadcrumb() }, immediate: true } }, methods: { getBreadcrumb () { let matched = this.$route.matched.filter( item => item.meta && item.meta.title ) const first = matched[0] // 这一部分根据自己的项目设置:我们的项目home页是不需要面包屑的,所以设置了breadcrumb: false if (this.$route.path === '/home') { matched = [ { path: '/', redirect: '/home', meta: { title: 'home', breadcrumb: false } } ].concat(matched) } else if (!this.isHome(first)) { // 根匹配只要不是home,就作为home下一级 matched = [ { path: '/', redirect: '/home', meta: { title: 'home' } } ].concat(matched) } this.levelList = matched.filter( item => item.meta && item.meta.title && item.meta.breadcrumb !== false ) }, isHome (route) { const name = route && route.name if (!name) { return false } return name.trim().toLocaleLowerCase() === 'home'.toLocaleLowerCase() }, pathCompile (path) { // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561 const { params } = this.$route var toPath = pathToRegexp.compile(path) return toPath(params) }, handleLink (item) { const { redirect, path } = item if (redirect) { this.$router.push(redirect) return } this.$router.push(this.pathCompile(path)) } } } .app-breadcrumb.el-breadcrumb { display: inline-block; font-size: 14px; line-height: 36px; background-color: #eeeeee; margin-bottom: 20px; width: 100%; padding-left: 10px; .no-redirect { color: #97a8be; cursor: text; } } 复制代码

处理面包屑这里,最重要的一点就是:要将路由嵌套做好,路由嵌套做好了,后续的面包屑处理起来就非常容易了。另外,path-to-regexp处理path有点问题,就是如果path上跟了?tab=2这样的参数时,点击面包屑的时候会报错,或许是我没找到path-to-regexp库的正确使用方式,我自己重新进行了处理才解决这个问题的。

服务封装 数据交互流程:

api service => request=>local mock/esay-mock/server api

axios拦截器 有时需要对请求头、响应进行统一处理 请求不同数据源时url会变化,需要根据环境自动修改url 可能出现的跨域问题 import axios from 'axios' import store from '../store/' import { Loading } from 'element-ui' let loading = null const service = axios.create({ baseURL: process.env.BASE_URL, // 请求地址 timeout: 40000, // request timeout withCredentials: true }) // request interceptor service.interceptors.request.use(config => { // config.headers['Accept'] = 'application/vnd.sd.v2+json' config.params = { ...config.params, // 混入 多语言 lang: store.getters.language } if (config.isDefaultLoading) { loading = Loading.service() } return config }, error => { Promise.reject(error) }) service.interceptors.response.use( response => { const res = response.data const { code } = res if (response.config.isDefaultLoading) { loading.close() } if (code !== 0) { // 未登录 if (code === 401 || code === 403) { if (window.location.pathname !== '/th/login') { store.dispatch('user/logout').then(() => { location.reload() }) } } // @params config.isExistCode // @doc 处理 服务端 特殊返回格式 // @default false if (!response.config.isExistCode) { return Promise.reject(res) } return Promise.reject(res) } else { return res } }, error => { return Promise.reject(error) } ) export default service 复制代码

BASE_URL是设置的环境变量,创建.env.development/.env.production文件 // base api BASE_API = '/dev-api'

数据mock

数据模拟两种常见方式,本地mock和线上esay-mock

本地mock

本地mock:修改vue.config.js,给devServer添加相关代码

安装 npm i body-parser -D 复制代码

注意:post请求需要额外安装依赖:body-parser,用于解析post请求时的body中的参数

vue.config.js const bodyParser = require("body-parser"); module.exports = { devServer: { before: app => { // 设置参数处理中间件,之后的每次请求都经过这两个中间件 app.use(bodyParser.json()); //处理post参数 app.use( bodyParser.urlencoded({ // 处理url参数 extended: true }) ); app.post("/th/api/login/api", (req, res) => { const { login_name,password } = req.body; if (login_name === "admin" && Number(password) === 123456) { res.json({ code: 0, message:'登录成功', msg:'登录成功', data: login_name }); } else { res.json({ code: -1, message: "用户名或密码错误", msg:'用户名或密码错误' }); } }); } } } } 复制代码

注意:本地mock了数据,服务端同时也提供了相关的接口,优先走本地的接口,服务端的接口是不请求的。 其实本地mock数据这种方式,是需要懂一些服务端代码的,而且前端自己mock数据工作量蛮大的,那咱们一起去看一下更优的方式mockjs与easy-mock

mockjs 介绍 数据类型丰富

支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。

拦截 Ajax 请求

不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。安全又便捷

API Mock.mock(url, type, data)

使用 安装 yarn add mockjs -D 复制代码 简单使用 import Mock from 'mockjs' let dataSource = Mock.mock({ 'dataSource|5': [{ 'key|+1': 1, 'mockTitle|1': ['哑巴', 'Butter-fly', '肆无忌惮', '摩天大楼', '初学者'], 'mockContent|1': ['你翻译不了我的声响', '数码宝贝主题曲', '摩天大楼太稀有', '像海浪撞破了山丘'], 'mockAction|1': ['下载', '试听', '喜欢'] }] }) export default dataSource 复制代码

以请求的方式mock数据 // mock/api.js // api.js import Mock from 'mockjs' const url = { tableDataOne: 'http://20181024Mock.com/mode1/tableDataOne', tableDataTwo: 'http://20181024Mock.com/mode1/tableDataTwo', tableDataThi: 'http://20181024Mock.com/mode1/tableDataThi' } export default [ Mock.mock(url.tableDataOne, { message: '获取成功', msg: '操作成功', code: 0, 'data|5': [{ 'key|+1': 1, 'mockTitle|1': ['哑巴', 'Butter-fly', '肆无忌惮', '摩天大楼', '初学者'], 'mockContent|1': ['你翻译不了我的声响', '数码宝贝主题曲', '摩天大楼太稀有', '像海浪撞破了山丘'], 'mockAction|1': ['下载', '试听', '喜欢'] }] }) ] // 请求数据 import '@/mock/api.js' import $axios from 'axios' $axios.get('http://20181024Mock.com/mode1/tableDataOne').then(res => { if (res.status === 200) { let data = res.data.data console.log(data) } }) 复制代码 创建node服务 mock/mock-server.js let express = require('express') // 引入express let Mock = require('mockjs') // 引入mock let app = express() // 实例化express // 解决跨域问题 app.use(function (req, res, next) { res.header('Access-Control-Allow-Origin', '*') res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS') res.header('Access-Control-Allow-Headers', 'X-Requested-With') res.header('Access-Control-Allow-Headers', 'Content-Type') next() }) app.use('/mode2/DataOne', function (req, res) { res.json(Mock.mock({ 'status': 200, 'code': 0, 'data|1-9': [{ 'key|+1': 1, 'mockTitle|1': ['肆无忌惮'], 'mockContent|1': ['角色精湛主题略荒诞', '理由太短 是让人不安', '疑信参半 却无比期盼', '你的惯犯 圆满', '别让纠缠 显得 孤单'], 'mockAction|1': ['下载', '试听', '喜欢'] }] })) }) app.listen('3333', () => { console.log('监听端口 3333') }) // 获取数据 $axios.get('http://localhost:3333/mode2/DataOne').then(res => { if (res.status === 200) { let data = res.data.data console.log(data) } }) 复制代码easy-mock

easy-mock实现了cors,所以咱们使用的时候,不用担心跨域的问题。

准备工作

安装nvm

windows

mac

使用nvm安装node8.x:nvm install 8.16.0

安装mongodb

windows

mac

安装 redis

windows

mac

克隆easy-mock项目

git clone https://github.com/easy-mock/easy-mock.git 复制代码 步骤

因为easy-mock的官网不是太稳定,自己在本地搭建一个easy-mock的服务器

启动redis redis-server 启动mongodb mongod 切换node版本 nvm use 8.16 启动easy-mock npm run dev 登录easy-mock 创建项目 创建需要的接口 修改base_url,.env.development VUE_APP_BASE_API = 'https://easy-mock.com/mock/5cdcc3fdde625c6ccadfd70c/kkb- cart' 复制代码 项目中直接调用相关接口即可 解决跨域

现在开发基本都是前后端分离的,接口服务器会与前端分离开来,所以经常情况下会造成跨域的问题。那么跨域到底是前端的问题还是后端的问题呢?所有的跨域问题一定是后端的问题,跟前端关系不大,但是前端是有办法解决的。

浏览器的问题引起的,只要发出去的请求和当前网站所在的地址不一样(包括:协议、端口号、子域名),会阻止请求发出去,有以下几种方式解决跨域问题:

jsonp script的src,get请求(其实是躲避,没有真正解决)现在基本已经淘汰了 cors options预处理(浏览器会发送2次请求)公共api一般用这种方式 proxy nginx代理,不直接与api server打交道就不会跨域,服务器请求服务器是不存在跨域问题的

webpack中配置proxy代理

devServer:{ port:port, proxy: { // 代理/th/api/login 到http://scm.ksher.cn/th/api/login [process.env.BASE_URL]: { target: "http://scm.ksher.cn", changeOrigin: true, pathRewrite:{ // 重写之后:代理/th/api/login 到http://scm.ksher.cn/login ["^"+process.env.BASE_URL]:"" } }, } 复制代码

注意:如果接口本身没有/th/api,则需要通过pathRewrite来重写了地址

参考

手摸手,带你优雅的使用 icon



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有